using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Namuc.Utility;

namespace Namuc.MathLibrary
{
    /// <summary>
    /// Provides the common interface of any vector used by <b>NAMUC</b>
    /// math library
    /// </summary>
    public interface IVector : IFormattable, IEquatable<IVector>, ICloneable
    {
        /// <summary>
        /// Gets the number of components of current vector.
        /// </summary>
        int NumberOfComponents
        {
            get;
        }

        /// <summary>
        /// Sets/Gets the value of a vector component.
        /// </summary>
        /// <param name="componentIndex">Index (0-based) of component to sets/gets.</param>
        double this [ int componentIndex ]
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the value of norm 2 of this vector.
        /// </summary>
        double Norm
        {
            get;
        }

        /// <summary>
        /// Gets the lenght of this vector.
        /// </summary>
        double Lenght
        {
            get;
        }

        /// <summary>
        /// Gets the normalize form of this vector.
        /// </summary>
        IVector Normalized
        {
            get;
        }
    }

    /// <summary>
    /// Represents a generic vector having N components.
    /// </summary>
    public class Vector : IVector
    {
        #region IVector Members

        /// <summary>
        /// Create a new instance of <see cref="Vector"/> using the passed components
        /// </summary>
        /// <param name="isNormalized"><c>true</c> if vector to create is normalized</param>
        /// <param name="components">Vector components</param>
        private Vector( bool isNormalized, double [] components )
        {
            if ( components == null )
                throw new ArgumentNullException( "components" );
            // Clone the passed array avoiding side effects
            this.components = ( double [] )components.Clone();
            numberOfComponents = components.Length;
            // If this vector is not normalized, the normalized vector must
            // be created
            if ( !isNormalized )
                normalized = new Vector( true, new double [ numberOfComponents ] );
            else
                // This vector is already normalized, doesn't need the normalization
                // of a normalized vector
                normalized = null;
            // Recalculation needed
            componentsAreChanged = true;
        }

        /// <summary>
        /// Creates a new <see cref="Vector"/> having the passed number
        /// of components.
        /// </summary>
        /// <remarks>
        /// All components are set to zero.
        /// </remarks>
        /// <param name="numberOfComponents">Number of components.</param>
        public Vector( int numberOfComponents )
            : this( new double [ numberOfComponents ] )
        {
        }

        /// <summary>
        /// Creates a new <see cref="Vector"/> having as components
        /// the passed array.
        /// </summary>
        /// <remarks>
        /// <para>
        /// First component of new vector is set with the value
        /// of first element of passed array, and so on.
        /// </para>
        /// <para>
        /// Passed array is cloned, avoiding side-effects.
        /// </para>
        /// </remarks>
        /// <param name="components">Components of new vector.</param>
        public Vector( double [] components )
            : this( false, components )
        {
        }

        private int numberOfComponents;

        /// <summary>
        /// Gets the number of components of current vector.
        /// </summary>
        public int NumberOfComponents
        {
            get
            {
                return numberOfComponents;
            }
        }

        private bool checkComponentIndex( int compIndex, bool throwException )
        {
            // This method checks if passed component index is valid
            if ( compIndex < 0 || compIndex >= numberOfComponents )
            {
                // Passed index is not valid
                if ( throwException )
                    // When "throwException" flag is true this method rises an exception
                    throw new ArgumentOutOfRangeException( "component index" );
                else
                    return false;
            }
            // Passed index is ok
            return true;
        }

        /// <summary>
        /// Holds vector components
        /// </summary>
        private readonly double [] components;

        /// <summary>
        /// This flag indicates if any components have been change
        /// since last time that method <see cref="recalculateData"/> has been
        /// called
        /// </summary>
        /// <remarks>
        /// The following properties: <see cref="Lenght"/>, <see cref="Norm"/>, <see cref="Normalized"/>
        /// must be recalculated when one or more components are changed. This flag
        /// is checked in all get-accessor of this properties and when it is true
        /// all value are recalculated. This strategy should emprove performance,
        /// avoiding useless recalculation at each component changes.
        /// </remarks>
        private bool componentsAreChanged;

        /// <summary>
        /// Sets/Gets the value of a vector component.
        /// </summary>
        /// <param name="componentIndex">Index (0-based) of component to sets/gets.</param>
        /// <exception cref="ArgumentOutOfRangeException">Passed component index is not valid.</exception>
        public double this [ int componentIndex ]
        {
            get
            {
                checkComponentIndex( componentIndex, true );
                return components [ componentIndex ];
            }
            set
            {
                checkComponentIndex( componentIndex, true );
                components [ componentIndex ] = value;
                // A component has been changed, vector properties might be
                // changed, they have to be recalculated.
                componentsAreChanged = true;
            }
        }

        /// <summary>
        /// If this vector is a normalized vector this property returns <c>true</c>.
        /// </summary>
        public bool IsNormal
        {
            get
            {
                // If the normalized vector has not been created by
                // constructor it means this vector is a normalized vector.
                // It make no sense to create a normalized vector of
                // a normalized vector (and it brings in a infite structure)
                return normalized == null;
            }
        }

        #region Recalculated properties at each component change
        private double norm;
        private double length;
        private readonly IVector normalized;

        private void recalculateData()
        {
            // This method should be called only when componentsAreChanged is true
            Debug.Assert( componentsAreChanged, "Recalculation of internal data was not really required!" );

            #region Update norm
            norm = 0;
            for ( int i = 0; i < numberOfComponents; i++ )
                norm += components [ i ] * components [ i ];
            #endregion
            #region Update lenght
            // TODO: performance
            length = Math.Sqrt( norm );
            #endregion
            Debug.Assert( length >= 0, "Vector lenght must be equal or greater than zero!!!" );
            #region Update normal
            // Update normal only if lenght is not zero!
            // And current vector is not a normalized vector
            if ( length > 0 && !IsNormal )
                for ( int i = 0; i < numberOfComponents; i++ )
                    normalized[ i ] = components [ i ] / length;
            #endregion

            // All recalculation have been made!
            componentsAreChanged = false;
        }
        #endregion

        /// <summary>
        /// Gets the norm-2 of this vector.
        /// </summary>
        public double Norm
        {
            get
            {
                if ( componentsAreChanged )
                    recalculateData();

                return norm;
            }
        }

        /// <summary>
        /// Gets the lenght of this vector.
        /// </summary>
        public double Lenght
        {
            get
            {
                if ( componentsAreChanged )
                    recalculateData();

                return norm;
            }
        }

        /// <summary>
        /// Gets the normalize form of this vector.
        /// </summary>
        public IVector Normalized
        {
            get
            {
                if ( !IsNormal )
                {
                    // This vector is not a normalized vector
                    // We can calculate the normalized one
                    if ( componentsAreChanged )
                        recalculateData();

                    return normalized;
                }
                // This vector is already a normalized vector
                // The normalized vector of a normalized vector is itself
                return this;
            }
        }
        #endregion

        #region IFormattable Members

        public string ToString( string format, IFormatProvider formatProvider )
        {
            string txt = "[";

            for ( int i = 0; i < NumberOfComponents - 1; i++ )
                txt += components [ i ] + ", ";

            txt += components [ NumberOfComponents - 1 ] + "]";

            return txt;
        }

        #endregion

        #region IEquatable<IVector> Members

        const double RELATIVE_COMPARATION_TOLERANCE = 0.00001;
        const double ABSOLUTE_COMPARATION_TOLERANCE = 0.00001;

        public bool Equals( IVector other )
        {
            Debug.Assert( other != null, "Passed vector is null!" );
            if ( other.NumberOfComponents != NumberOfComponents )
                // Different number of components... Cannot compare!
                return false;

            for ( int i = 0; i < NumberOfComponents; i++ )
            {
                if ( !compareWithTollerance( this.components [ i ], other [ i ] ) )
                    // If at least one differs... The two vectors are considered different
                    return false;
            }
            // Ok, they are equal!
            return true;
        }

        private static bool compareWithTollerance( double value1, double value2 )
        {
            if ( value1 == value2 )
                return true;

            // TODO: inserire comparazione con tolerance (assoluta e relativa)
            return false;
        }

        #endregion

        #region ICloneable Members

        public object Clone()
        {
            return new Vector( components );
        }

        #endregion
    }
}
